using System;
using System.Linq;
using System.Reflection;
using System.Linq.Expressions;
using Ex = System.Linq.Expressions.Expression;

namespace Duhking
{
    internal delegate object LateBoundMethod(object target, object[] arguments);
    internal delegate void VoidLateBoundMethod(object target, object[] arguments);

    internal static class DynamicMethodExtentions
    {
        internal static LateBoundMethod CreateMethodExecuter(this MethodInfo methodInfo)
        {
            var instanceArgument = Ex.Parameter(typeof(object), "instance");
            var paramsArgument = Ex.Parameter(typeof(object[]), "parameters");

            var ex = Ex.Call(Ex.Convert(instanceArgument, methodInfo.DeclaringType), methodInfo, CreateParameterExpression(methodInfo, paramsArgument));
            var lambda = Ex.Lambda<LateBoundMethod>(Ex.Convert(ex, typeof(object)), instanceArgument, paramsArgument);
            return lambda.Compile();
        }

        internal static VoidLateBoundMethod CreateVoidMethodExecuter(this MethodInfo methodInfo)
        {
            var instanceArgument = Ex.Parameter(typeof(object),"instance");
            var paramsArgument = Ex.Parameter(typeof(object[]), "parameters");
            var ex = Ex.Call(Ex.Convert(instanceArgument, methodInfo.DeclaringType), methodInfo, CreateParameterExpression(methodInfo, paramsArgument));
            var lambda = Ex.Lambda<VoidLateBoundMethod>(ex, instanceArgument, paramsArgument);
            return lambda.Compile();
        }

        internal static Delegate CreateMethodExecuterDelegate(this MethodInfo methodInfo)
        {
            if (methodInfo.ReturnType == typeof(void))
                return methodInfo.CreateVoidMethodExecuter();
            return methodInfo.CreateMethodExecuter();
        }

        internal static Delegate CreateDuhkingExecuter(this MethodInfo methodInfo, Type targetType, InterfaceDuhker duhker)
        {
            var instanceArgument = Ex.Parameter(typeof(object), "instance");
            var paramsArgument = Ex.Parameter(typeof(object[]), "parameters");

            var ex = Ex.Call(Ex.Convert(instanceArgument, methodInfo.DeclaringType), methodInfo, CreateParameterExpression(methodInfo, paramsArgument));
            var duhkerEx = Ex.Constant(duhker);
            var interfaceArgument = Ex.Constant(targetType);
            
            MethodInfo duhkMethod = typeof(InterfaceDuhker).GetMethod("Duhk", new Type[] { typeof(Type), typeof(object) });
            var finalEx = Ex.Call(duhkerEx, duhkMethod, interfaceArgument, Ex.Convert(ex,typeof(object)));
            var lambda = Ex.Lambda<LateBoundMethod>(Ex.Convert(finalEx, typeof(object)), instanceArgument, paramsArgument);
            return lambda.Compile();
        }

        private static Expression[] CreateParameterExpression(MethodInfo methodInfo, ParameterExpression paramsArgument)
        {
            return methodInfo.GetParameters().Select((p, i) =>
                Ex.Convert(Ex.ArrayIndex(paramsArgument, Ex.Constant(i)), p.ParameterType)).ToArray();
        }
    }
}
